/* 
 * Copyright (C)2008 Vladimir Hozjan vladimir.hozjan@gmail.com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */

/**
 * @file  MainForm.cpp
 *
 * Implementation of main form for vwTetris
 *
 * @author Vladimir Hozjan
 * @date   25.12.2008 
 *
 * @remarks VHOZ [25.12.2008]: Class created
 */
#include "wxMainForm.h"
#include "tetris.h"

/* for random */
#include <stdio.h>
#include <time.h>
#include <math.h>

#define BOARD_ROW (20)
#define BOARD_COL (10)
#define CELL_SIZE (20)

/**
 * @enum for wx callback IDs
 */
enum {
  ID_New = wxID_HIGHEST,
  ID_Close,
  ID_Timer_Game
};

BEGIN_EVENT_TABLE(MainForm, wxFrame)
  EVT_MENU(ID_New, MainForm::onNewGame)
  EVT_MENU(ID_Close, MainForm::onQuit)
  EVT_TIMER(ID_Timer_Game, MainForm::onTimerExpired)
END_EVENT_TABLE()

BEGIN_EVENT_TABLE(MyBoard, wxPanel)
  EVT_KEY_DOWN(MyBoard::onKeyDown)
  EVT_PAINT(MyBoard::onPaint)
  EVT_ERASE_BACKGROUND(MyBoard::onEraseBackground)
END_EVENT_TABLE()

BEGIN_EVENT_TABLE(MyNext, wxPanel)
  EVT_PAINT(MyNext::onPaint)
  EVT_ERASE_BACKGROUND(MyNext::onEraseBackground)
END_EVENT_TABLE()

static wxColour sShapeColors[] = { 
  wxColour(255, 255, 255), 
  wxColour(204, 102, 102), 
  wxColour(102, 204, 102), 
  wxColour(102, 102, 204), 
  wxColour(204, 204, 102), 
  wxColour(204, 102, 204), 
  wxColour(102, 204, 204), 
  wxColour(218, 170, 0) 
};

static wxColour sShapeColorsLight[] = { 
  wxColour(255, 255, 255), 
  wxColour(248, 159, 171),
  wxColour(121, 252, 121), 
  wxColour(121, 121, 252), 
  wxColour(252, 252, 121), 
  wxColour(252, 121, 252), 
  wxColour(121, 252, 252), 
  wxColour(252, 198, 0)
};

static wxColour sShapeColorsDark[] = { 
  wxColour(255, 255, 255),
  wxColour(128, 59, 59), 
  wxColour(59, 128, 59), 
  wxColour(59, 59, 128), 
  wxColour(128, 128, 59), 
  wxColour(128, 59, 128), 
  wxColour(59, 128, 128), 
  wxColour(128, 98, 0) 
};
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////

/**
 * @class TestRandomImpl
 * 
 * Null random implementation of random interface
 *
 * @author VHOZ
 *
 * @remarks VHOZ [25.12.2008]: Class created
 */
class RandomImpl : public SystemInterface {
public:
  /// Constructor
  RandomImpl(){
    /* initialize random seed: */
    srand ( time(0) );
  };

  /// Destructor
  virtual ~RandomImpl(){};

  /// return random number
  virtual int getRandom(int aMaxValue) {
    return (rand() % aMaxValue) ;
  }
  
  /// return sqrt
  virtual double sqrt(double aVal){
    return ::sqrt(aVal);
  }
};

/*static null random object*/
static RandomImpl randomObject;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/**
 * MainForm
 *
 * Constructor
 *
 * @param aName - name of frame
 */
MainForm::MainForm( const wxString &aName ) :
wxFrame(NULL, wxID_ANY, aName),
mTetris(0),
mTimer(0)
{
  mTimer = new wxTimer(this, ID_Timer_Game);

  wxMenuBar *menubar = new wxMenuBar;
  wxMenu *file = new wxMenu;
  file->Append(ID_New, wxT("&New game"));
  file->AppendSeparator();
  file->Append(ID_Close, wxT("&Quit"));

  menubar->Append(file, wxT("&File"));

  SetMenuBar(menubar);

  mTopsizer = new wxBoxSizer(wxHORIZONTAL);
  wxBoxSizer *leftSizer = new wxBoxSizer(wxVERTICAL);

  mBoard = new MyBoard(this, BOARD_ROW, BOARD_COL, CELL_SIZE);
  
  mTopsizer->Add(mBoard, 0, wxALL | wxFIXED_MINSIZE, 5);
  mTopsizer->Add(leftSizer, 0, wxTOP | wxLEFT | wxALIGN_LEFT, 5);

  mNext = new MyNext(this, Tetris::MAX_ELEMENT_DIMENSION, CELL_SIZE);
  wxFont captionFont(14, wxROMAN, wxNORMAL, wxBOLD);  
  wxFont dataFont(16, wxROMAN, wxNORMAL, wxBOLD);  

  wxStaticText *sNext = new wxStaticText(this, wxID_ANY, wxT("NEXT"));
  sNext->SetFont(captionFont);

  wxStaticText *sScore = new wxStaticText(this, wxID_ANY, wxT("SCORE"));
  sScore->SetFont(captionFont);
  mScore = new wxStaticText(this, wxID_ANY, wxT("0"));
  mScore->SetFont(dataFont);

  wxStaticText *sLevel = new wxStaticText(this, wxID_ANY, wxT("LEVEL"));
  sLevel->SetFont(captionFont);
  mLevel = new wxStaticText(this, wxID_ANY, wxT("0"));
  mLevel->SetFont(dataFont);

  wxStaticText *sLines = new wxStaticText(this, wxID_ANY, wxT("LINES"));
  sLines->SetFont(captionFont);
  mLines = new wxStaticText(this, wxID_ANY, wxT("0"));
  mLines->SetFont(dataFont);

  leftSizer->Add(sNext, 0, wxALL | wxALIGN_CENTER_HORIZONTAL, 5);
  leftSizer->Add(mNext, 0, wxALL | wxALIGN_CENTER_HORIZONTAL, 5);
  leftSizer->Add(sScore, 0, wxALL | wxALIGN_CENTER_HORIZONTAL, 5);
  leftSizer->Add(mScore, 0, wxALL | wxALIGN_CENTER_HORIZONTAL, 5);
  leftSizer->Add(sLevel, 0, wxALL | wxALIGN_CENTER_HORIZONTAL, 5);
  leftSizer->Add(mLevel, 0, wxALL | wxALIGN_CENTER_HORIZONTAL, 5);
  leftSizer->Add(sLines, 0, wxALL | wxALIGN_CENTER_HORIZONTAL, 5);
  leftSizer->Add(mLines, 0, wxALL | wxALIGN_CENTER_HORIZONTAL, 5);

  SetSizer(mTopsizer);
  mTopsizer->SetSizeHints(this); 
  Centre();
}

/**
 * ~MainForm
 *
 * Destructor
 */
MainForm::~MainForm() {
  if (mTetris) {
    delete mTetris;
  }
  if (mTimer) {
    delete mTimer;
  }
}

/**
 * onNewGame
 *
 * Start tetris game
 *
 * @param event
 */
void MainForm::onNewGame( wxCommandEvent& event ) {
  if (mTetris) {
    delete mTetris;
  }

  mTetris = new Tetris(randomObject, BOARD_ROW, BOARD_COL);
  TTetrisFunctor<MainForm> positionCallback(this,&MainForm::onTetrisChange);
  TTetrisFunctor<MainForm> elementCallback(this,&MainForm::onTetrisElementChange);
  mTetris->setPositionChangeCallback(positionCallback);
  mTetris->setElementChangeCallback(elementCallback);

  mTimer->Start(mTetris->getDelay(), true);
}

/**
 * onQuit
 *
 * Quit application and deletes tetris game
 *
 * @param event
 */
void MainForm::onQuit( wxCommandEvent& event ) {
  Close(true);
}

/**
 * onTetrisChange
 *
 * @return void
 */
void MainForm::onTetrisChange() {
  /*Redraw tetris*/
  mBoard->Refresh();
}

/**
 * onTetrisElementChange
 *
 * New element apeared on tetris board
 *
 */
void MainForm::onTetrisElementChange() {
  mNext->Refresh();
  mScore->SetLabel(wxString::Format(wxT("%d"), (int)mTetris->getScore()));
  mLines->SetLabel(wxString::Format(wxT("%d"), (int)mTetris->getNumberOfLines()));
  mLevel->SetLabel(wxString::Format(wxT("%d"), (int)mTetris->getLevel() + 1));
  mTopsizer->RecalcSizes();
  mTopsizer->SetSizeHints(this); 
}

/**
 * onTimerExpired
 *
 * Timer has expired
 *
 * @param[in] aEvent - event object
 */
void MainForm::onTimerExpired( wxTimerEvent & aEvent ) {
  mTetris->nextMove();
  mTimer->Start(mTetris->getDelay(), true);
}

/**
 * MyBoard
 *
 * Constructor
 * 
 * @param[in] parent - parent frame
 *
 */
MyBoard::MyBoard( MainForm *aParent, unsigned short aRow, unsigned short aCol, unsigned short aSize ): 
wxPanel(aParent, wxID_ANY, wxPoint(0,0), wxSize(aCol * aSize, aRow * aSize), wxBORDER_NONE),
mMaxRow(aRow),
mMaxCol(aCol),
mSize(aSize),
mLineWidth(1),
mParent(aParent)
{
}

/**
 * onKeyDown
 *
 * Key down event
 *
 * @param[in] aEvent - event object
 */
void MyBoard::onKeyDown( wxKeyEvent & aEvent ) {
  int key = aEvent.GetKeyCode();
  
  Tetris *tetris = mParent->getTetris();
  if (tetris == 0) {
    return;
  }

  switch(key) {
  case WXK_LEFT : tetris->moveLeft();  break;
  case WXK_RIGHT: tetris->moveRight(); break;
  case WXK_DOWN:  tetris->moveDown(); break;
  case WXK_UP:    tetris->rotateRight(); break;
  case WXK_SPACE: tetris->drop(); break;
  default:
    aEvent.Skip();
  }
}

/**
 * OnPaint
 *
 * On paint for wxPanel
 *
 * @param[in] wxPaintEvent & event
 *
 */
void MyBoard::onPaint( wxPaintEvent& event ) {
  int i, j;
  wxBufferedPaintDC dc(this);

  dc.SetPen(*wxWHITE_PEN);
  dc.SetBrush(wxBrush(wxColor(255,255,255))); 
  dc.DrawRectangle(0, 0, mMaxCol * mSize, mMaxRow * mSize);

  Tetris *tetris = mParent->getTetris();
  if (tetris == 0) {
    return;
  }

  for (i = 0; i < mMaxRow; i++) {
    for (j = 0; j < mMaxCol; j++) {
      unsigned short idx = tetris->getCell(i,j);
      drawRect(dc, j, mMaxRow - 1 - i, idx);
    }
  }


  if (tetris->isGameOver()) {
    long width, height;
    wxString gameOver(wxT("GAME OVER"));
    dc.SetFont(wxFont(20, wxROMAN, wxNORMAL, wxBOLD));
    dc.SetTextForeground(*wxRED);
    dc.GetTextExtent(gameOver, &width, &height);
    dc.DrawText(wxT("GAME OVER"), (mMaxCol * mSize - width)/ 2, (mMaxRow * mSize - height)/ 2);
  }
}

/**
 * drawRect
 *
 * Draw rectangle at given position.
 *
 * @param[in] dc - device context
 * @param[in] x
 * @param[in] y
 * @param[in] color
 *
 */
void MyBoard::drawRect( wxBufferedPaintDC & dc, int x, int y, int color ) {
  if (color == 0 || color > 7) {
    return;
  }

  int startx = mSize * x;
  int starty = mSize * y;

  int endx = mSize * (x + 1);
  int endy = mSize * (y + 1); 

  wxPen pen(sShapeColorsLight[int(color)]);
  pen.SetCap(wxCAP_PROJECTING);
  pen.SetWidth(mLineWidth);
  dc.SetPen(pen);

  dc.DrawLine(startx, starty , endx - mLineWidth , starty);
  dc.DrawLine(startx, starty , startx  , endy - mLineWidth);

  wxPen darkpen(sShapeColorsDark[int(color)]);
  darkpen.SetCap(wxCAP_PROJECTING);
  darkpen.SetWidth(mLineWidth);
  dc.SetPen(darkpen);

  dc.DrawLine(endx - mLineWidth, endy - mLineWidth, startx  , endy - mLineWidth);
  dc.DrawLine(endx - mLineWidth, endy - mLineWidth, endx - mLineWidth, starty);

  dc.SetPen(*wxTRANSPARENT_PEN);
  dc.SetBrush(wxBrush(sShapeColors[int(color)])); 
  dc.DrawRectangle(startx + mLineWidth, starty + mLineWidth, mSize - 2*mLineWidth, mSize - 2*mLineWidth);
}

/**
 * MyNext
 *
 * Constructor
 *
 * @param[in] parent
 *
 */
MyNext::MyNext( MainForm *aParent, unsigned short aMaxDimension,  unsigned short aSize): 
wxPanel(aParent, wxID_ANY, wxPoint(0,0), wxSize(aMaxDimension * aSize, aMaxDimension * aSize), wxBORDER_NONE),
mMaxRow(aMaxDimension),
mMaxCol(aMaxDimension),
mSize(aSize),
mLineWidth(1),
mParent(aParent) 
{
}

/**
 * onPaint
 *
 * On paint method called by the system
 *
 * @param[in] aEvent
 */
void MyNext::onPaint( wxPaintEvent& aEvent ) {
  int i, j;

  wxBufferedPaintDC dc(this);
  dc.SetPen(wxPen(mParent->GetBackgroundColour()));
  dc.SetBrush(wxBrush(mParent->GetBackgroundColour())); 
  dc.DrawRectangle(0, 0, mMaxCol * mSize, mMaxRow * mSize);

  Tetris *tetris = mParent->getTetris();
  if (tetris == 0 ) {
    return;
  }

  unsigned short elSize = tetris->getDimensionOfNextElement();

  for (i = 0; i < elSize; i++) {
    for (j = 0; j < elSize; j++) {
      unsigned short idx = tetris->getCellOfNextElement(i,j);
      drawRect(dc, j, elSize - 1 - i, idx);
    }
  }
}

/**
 * drawRect
 *
 * long_description
 *
 * @param[in] wxBufferedPaintDC & dc
 * @param[in] int x
 * @param[in] int y
 * @param[in] int color
 */
void MyNext::drawRect( wxBufferedPaintDC& dc, int x, int y, int color ) {
  if (color == 0 || color > 7) {
    return;
  }

  int startx = mSize * x;
  int starty = mSize * y;

  int endx = mSize * (x + 1);
  int endy = mSize * (y + 1); 

  wxPen pen(sShapeColorsLight[int(color)]);
  pen.SetCap(wxCAP_PROJECTING);
  pen.SetWidth(mLineWidth);
  dc.SetPen(pen);

  dc.DrawLine(startx, starty , endx - mLineWidth , starty);
  dc.DrawLine(startx, starty , startx  , endy - mLineWidth);

  wxPen darkpen(sShapeColorsDark[int(color)]);
  darkpen.SetCap(wxCAP_PROJECTING);
  darkpen.SetWidth(mLineWidth);
  dc.SetPen(darkpen);

  dc.DrawLine(endx - mLineWidth, endy - mLineWidth, startx  , endy - mLineWidth);
  dc.DrawLine(endx - mLineWidth, endy - mLineWidth, endx - mLineWidth, starty);

  dc.SetPen(*wxTRANSPARENT_PEN);
  dc.SetBrush(wxBrush(sShapeColors[int(color)])); 
  dc.DrawRectangle(startx + mLineWidth, starty + mLineWidth, mSize - 2*mLineWidth, mSize - 2*mLineWidth);
}
